//
//  LcNetworkCenter.m
//  18Birdies
//
//  Created by Lc on 15/3/20.
//  Copyright (c) 2015年 Lc. All rights reserved.
//

#import "LcNetworkCenter.h"
#import "LcNetworkException.h"
#import "LcPersistenceCenter.h"
#import "LcPreconditions.h"
#import "NSDate+Lc.h"
#import "NSString+Lc.h"

@interface LcNetworkCenter ()
@property(nonatomic, strong) NSOperationQueue* operationQueue;
@property(nonatomic, strong) LcPersistenceCenter* persistenceCenter;
@end

@implementation LcNetworkCenter

#pragma mark - Single Instance

- (void)internalInit {
  self.operationQueue = [[NSOperationQueue alloc] init];
  self.operationQueue.maxConcurrentOperationCount = 3;
  _multipartBoundary = @"LcFoundationMultipartBoundary";
  self.persistenceCenter = [LcPersistenceCenter sharedInstance];
}

#pragma mark -

/*
 Caution: completionBlock is not excuted on the caller thread
 */
- (void)httpGet:(NSString*)urlString
      withParameters:(NSDictionary*)parameters
             headers:(NSDictionary*)headers
    completionHandler:(void (^)(NSHTTPURLResponse* response,
                               NSData* responseBody,
                               NSError* error))completionHandler {
  LC_CHECK_NOT_EMPTY(urlString);
  LC_CHECK_NOT_NULL(completionHandler);
  if (![urlString hasPrefix:@"http://"] && ![urlString hasPrefix:@"https://"]) {
    @throw
        [LcNetworkException exceptionWithReason:@"url is not start with http"];
  }
  NSString* urlStringWithParameters =
      [self urlString:urlString withParameters:parameters];
  NSURL* url = [NSURL URLWithString:urlStringWithParameters];
  NSMutableURLRequest* urlRequest =
      [NSMutableURLRequest requestWithURL:url
                              cachePolicy:NSURLRequestUseProtocolCachePolicy
                          timeoutInterval:15];
  urlRequest.HTTPMethod = @"GET";
  urlRequest.allHTTPHeaderFields = headers;
  NSOperation* operation = [NSBlockOperation blockOperationWithBlock:^{
    NSURLSessionDataTask * dataTask =
      [NSURLSession.sharedSession dataTaskWithRequest:urlRequest
                                    completionHandler:^(NSData* data,
                                                        NSURLResponse* response,
                                                        NSError* error) {
      LC_CHECK_TRUE(response == nil || [response isKindOfClass:NSHTTPURLResponse.class]);
      completionHandler((NSHTTPURLResponse*)response, data, error);
    }];
    [dataTask resume];
  }];
  [self.operationQueue addOperation:operation];
}

/*
 Caution: completionBlock is not excuted on the caller thread
 */
- (void)httpPost:(NSString*)urlString
        withBody:(NSData*)body
         headers:(NSDictionary*)headers
completionHandler:(void (^)(NSHTTPURLResponse* response,
                           NSData* responseBody,
                           NSError* error))completionHandler {
  LC_CHECK_NOT_EMPTY(urlString);
  LC_CHECK_NOT_NULL(completionHandler);
  NSURL* url = [NSURL URLWithString:urlString];
  NSMutableURLRequest* urlRequest =
      [NSMutableURLRequest requestWithURL:url
                              cachePolicy:NSURLRequestUseProtocolCachePolicy
                          timeoutInterval:15];
  urlRequest.HTTPMethod = @"POST";
  urlRequest.HTTPBody = body;
  NSMutableDictionary* headersWithClientTimestamp =
      [NSMutableDictionary dictionary];
  if (headers.count > 0) {
    [headersWithClientTimestamp addEntriesFromDictionary:headers];
  }
  headersWithClientTimestamp[@"Timestamp"] =
      [NSString stringWithFormat:@"%lld", [NSDate currentTimeMillis]];
  urlRequest.allHTTPHeaderFields = headersWithClientTimestamp;
  NSOperation* operation = [NSBlockOperation blockOperationWithBlock:^{
    NSURLSessionDataTask * dataTask =
      [NSURLSession.sharedSession dataTaskWithRequest:urlRequest
                                  completionHandler:^(NSData* data,
                                                      NSURLResponse* response,
                                                      NSError* error) {
      LC_CHECK_TRUE(response == nil || [response isKindOfClass:NSHTTPURLResponse.class]);
      completionHandler((NSHTTPURLResponse*)response, data, error);
    }];
    [dataTask resume];
  }];
  [self.operationQueue addOperation:operation];
}

/*
 Caution: completionBlock is not excuted on the caller thread
 */
- (void)httpPost:(NSString*)urlString
     withStrings:(NSDictionary*)stringParts
           files:(NSDictionary*)fileParts
         headers:(NSDictionary*)headers
completionHandler:(void (^)(NSHTTPURLResponse* response,
                           NSData* responseBody,
                           NSError* error))completionHandler {
  LC_CHECK_NOT_EMPTY(urlString);
  LC_CHECK_NOT_NULL(completionHandler);
  NSURL* url = [NSURL URLWithString:urlString];
  NSMutableURLRequest* urlRequest =
      [NSMutableURLRequest requestWithURL:url
                              cachePolicy:NSURLRequestUseProtocolCachePolicy
                          timeoutInterval:15];
  urlRequest.HTTPMethod = @"POST";
  NSData* body =
      [self createMultipartBodyWithStrings:stringParts andFiles:fileParts];
  urlRequest.HTTPBody = body;
  if (headers == nil) {
    headers = [NSMutableDictionary dictionary];
  } else {
    headers = [headers mutableCopy];
  }
  [headers
      setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",
                                          self.multipartBoundary]
        forKey:@"Content-Type"];
  [headers
      setValue:[NSString stringWithFormat:@"%lld", [NSDate currentTimeMillis]]
        forKey:@"Timestamp"];
  urlRequest.allHTTPHeaderFields = headers;
  NSOperation* operation = [NSBlockOperation blockOperationWithBlock:^{
    NSURLSessionDataTask * dataTask =
      [NSURLSession.sharedSession dataTaskWithRequest:urlRequest
                                  completionHandler:^(NSData* data,
                                                      NSURLResponse* response,
                                                      NSError* error) {
      LC_CHECK_TRUE(response == nil || [response isKindOfClass:NSHTTPURLResponse.class]);
      completionHandler((NSHTTPURLResponse*)response, data, error);
    }];
    [dataTask resume];
  }];
  [self.operationQueue addOperation:operation];
}

- (NSData*)createMultipartBodyWithStrings:(NSDictionary*)stringParts
                                 andFiles:(NSDictionary*)fileParts {
  NSMutableData* multipartBody = [NSMutableData data];
  for (NSString* key in stringParts) {
    [multipartBody appendData:[NSString stringWithFormat:@"--%@\r\n",
                                                         self.multipartBoundary]
                                  .toData];
    [multipartBody
        appendData:[NSString stringWithFormat:@"Content-Disposition: "
                                              @"form-data; "
                                              @"name=\"%@\"\r\n\r\n",
                                              key]
                       .toData];
    NSString* value = stringParts[key];
    [multipartBody
        appendData:[NSString stringWithFormat:@"%@\r\n", value].toData];
  }
  for (NSString* key in fileParts) {
    [multipartBody appendData:[NSString stringWithFormat:@"--%@\r\n",
                                                         self.multipartBoundary]
                                  .toData];
    NSString* filePath = fileParts[key];
    NSString* fileName = filePath.lastPathComponent;
    [multipartBody
        appendData:[NSString
                       stringWithFormat:@"Content-Disposition: "
                                        @"form-data; "
                                        @"name=\"%@\"; filename=\"%@\"\r\n",
                                        key, fileName]
                       .toData];
    [multipartBody
        appendData:@"Content-Type: application/octet-stream\r\n\r\n".toData];
    NSData* fileData = [NSData dataWithContentsOfFile:filePath];
    if (fileData == nil) {
      @throw [LcNetworkException
          exceptionWithReason:
              [NSString stringWithFormat:@"file cannot be loaded, filePath=%@",
                                         filePath]];
    }
    [multipartBody appendData:fileData];
    [multipartBody appendData:@"\r\n".toData];
  }
  [multipartBody appendData:[NSString stringWithFormat:@"--%@--\r\n",
                                                       self.multipartBoundary]
                                .toData];
  return multipartBody;
}

- (NSData*)createMultipartBodyWithStrings:(NSDictionary*)stringParts
                                 andDatas:(NSDictionary*)dataParts {
  NSMutableData* multipartBody = [NSMutableData data];
  for (NSString* key in stringParts) {
    [multipartBody appendData:[NSString stringWithFormat:@"--%@\r\n",
                                                         self.multipartBoundary]
                                  .toData];
    [multipartBody
        appendData:[NSString stringWithFormat:@"Content-Disposition: "
                                              @"form-data; "
                                              @"name=\"%@\"\r\n\r\n",
                                              key]
                       .toData];
    NSString* value = stringParts[key];
    [multipartBody
        appendData:[NSString stringWithFormat:@"%@\r\n", value].toData];
  }
  for (NSString* key in dataParts) {
    [multipartBody appendData:[NSString stringWithFormat:@"--%@\r\n",
                                                         self.multipartBoundary]
                                  .toData];
    [multipartBody
        appendData:[NSString
                       stringWithFormat:@"Content-Disposition: "
                                        @"form-data; "
                                        @"name=\"%@\"; filename=\"%@\"\r\n",
                                        key, key]
                       .toData];
    [multipartBody
        appendData:@"Content-Type: application/octet-stream\r\n\r\n".toData];
    [multipartBody appendData:dataParts[key]];
    [multipartBody appendData:@"\r\n".toData];
  }
  [multipartBody appendData:[NSString stringWithFormat:@"--%@--\r\n",
                                                       self.multipartBoundary]
                                .toData];
  return multipartBody;
}

- (NSString*)urlString:(NSString*)urlString
        withParameters:(NSDictionary*)parameters {
  if (parameters.count == 0) {
    return urlString;
  }
  NSMutableString* urlStringWithParameters =
      [NSMutableString stringWithString:urlString];
  if (![urlStringWithParameters contains:@"?"]) {
    [urlStringWithParameters appendString:@"?"];
  }
  BOOL needAnd = [urlStringWithParameters contains:@"="];
  for (NSString* key in parameters) {
    NSString* value = parameters[key];
    if (needAnd) {
      [urlStringWithParameters appendString:@"&"];
    }
    [urlStringWithParameters appendString:key];
    [urlStringWithParameters appendString:@"="];
    [urlStringWithParameters appendString:value];
    needAnd = YES;
  }
  return urlStringWithParameters;
}

- (void)downloadImage:(NSString*)url
           saveToFile:(NSString*)fileName
      completeHandler:(ImageDownloadHandler)completeHandler {
  LC_CHECK_NOT_EMPTY(url);
  LC_CHECK_NOT_NULL(completeHandler);
  [self httpGet:url
        withParameters:nil
               headers:nil
      completionHandler:^(NSHTTPURLResponse* response, NSData* responseBody,
                         NSError* error) {
        UIImage* image = nil;
        if (error == nil && response.statusCode == 200 &&
            responseBody.length > 0) {
          image = [UIImage imageWithData:responseBody];
          if (image != nil && fileName.length > 0) {
            [self.persistenceCenter saveData:responseBody toFile:fileName];
          }
        }
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
          completeHandler(url, image);
        }];
      }];
}

- (void)downloadFile:(NSString*)url
          saveToFile:(NSString*)fileName
     completeHandler:(FileDownloadHandler)completeHandler {
  LC_CHECK_NOT_EMPTY(url);
  LC_CHECK_NOT_NULL(completeHandler);
  [self httpGet:url
        withParameters:nil
               headers:nil
      completionHandler:^(NSHTTPURLResponse* response, NSData* responseBody,
                         NSError* error) {
        BOOL success = NO;
        if (error == nil && response.statusCode == 200 &&
            responseBody.length > 0) {
          success = YES;
          if (fileName.length > 0) {
            [self.persistenceCenter saveData:responseBody toFile:fileName];
          }
        }
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
          completeHandler(url, success);
        }];
      }];
}

- (void)blockUntilFinished {
  [self.operationQueue waitUntilAllOperationsAreFinished];
}

@end
